#include <cmath>
#include "Perlin.hpp"

//Constructor
Perlin::Perlin(
		const int octaves,
		const double persistence,
		const double frequency,
		const int seed){
	octaves_ = octaves;
	persistence_ = persistence;
	frequency_ = frequency;
	seed_ = seed;
}
//Destructor
Perlin::~Perlin(void){ 
}

//Private subroutine
// Scurve quintic
inline double Perlin::scurve(const double a) const{
	double a3 = a * a * a;
	double a4 = a3 * a;
	double a5 = a4 * a;
	return (6.0 * a5) - (15.0 * a4) + (10.0 * a3);
}

inline double Perlin::noise(
		const int x,
		const int y, 
		const int z) const {
	int n = (1619 * x + 31337 * y + 6971 * z + 1013 * seed_) & 0x7fffffff;
	n = (n >> 13) ^ n;
	n = ((n * (n * n * 60493 + 19990303) + 1376312589) & 0x7fffffff);
	return(1.0f - ((double)n / 1073741824.0f));
}

double Perlin::interpolate(
		const double x,
		const double y,
		const double z) const{
	int ix0 = (x > 0.0f)? (int)x : ((int)x - 1);
	int ix1 = ix0 + 1;
	int iy0 = (y > 0.0f)? (int)y : ((int)y - 1);
	int iy1 = iy0 + 1;
	int iz0 = (z > 0.0f)? (int)z : ((int)z - 1);
	int iz1 = iz0 + 1; 

	double xs = scurve(x - (double)ix0);
	double ys = scurve(y - (double)iy0);
	double zs = scurve(z - (double)iz0);

	double A = noise(ix0,iy0,iz0)*(1-xs) + noise(ix1,iy0,iz0)*xs;
	double B = noise(ix0,iy1,iz0)*(1-xs) + noise(ix1,iy1,iz0)*xs;
	double AB  = A * (1-ys) + B * ys;

	double C = noise(ix0,iy0,iz1)*(1-xs) + noise(ix1,iy0,iz1)*xs;
	double D = noise(ix0,iy1,iz1)*(1-xs) + noise(ix1,iy1,iz1)*xs;
	double CD  = C * (1-ys) + D * ys;

	double ABCD  = AB * (1-zs) + CD * zs;
	return ABCD;
}

//Main Function
double Perlin::Perlinf(
		const double x,
		const double y,
		const double z) const{
	double total = 0.0f;
	double amp = 1.0f;
	double freq = 1.0f;

	for(int loop=0; loop < octaves_ ; loop++){
		total += interpolate(x*freq,y*freq,z*freq)*amp;
		amp *= persistence_;
		freq *= frequency_;
	}

	if(total > 1.0f)
		total = 1.0f;
	if(total < -1.0f)
		total = -1.0f;

	return total;
}

int Perlin::Perlini(
		const double x,
		const double y,
		const double z) const{
	double total = Perlinf(x,y,z); // [-1.0 ; 1.0]

	total += 1; // [0.0 ; 2.0]
	total = total*127.5f;
	return (int)total;	
}

//Accessors
int Perlin::GetOctaves(void) const{
	return octaves_;
}

double Perlin::GetPersistence(void) const{
	return persistence_; 
}

double Perlin::GetFrequency(void) const{
	return frequency_;
}

int Perlin::GetSeed(void) const{
	return seed_;
}

//Mutators
void Perlin::SetOctaves(const int value){
	octaves_ = value;
}

void Perlin::SetPersistence(const double value){
	persistence_ = value;
}

void Perlin::SetFrequency(const double value){
	frequency_ = value;
}

void Perlin::SetSeed(const int value){
	seed_ = value;
}

